/*
 * Copyright 2012 Gregor Schauer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.schauer.gregor.vienna.datastore.database;

import at.schauer.gregor.vienna.context.ViennaContext;
import at.schauer.gregor.vienna.datastore.DataStore;
import at.schauer.gregor.vienna.datastore.StorageMetadata;
import at.schauer.gregor.vienna.serializer.Serializer;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.DatabaseMetaDataCallback;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author Gregor Schauer
 */
public abstract class AbstractDatabaseDataStore<T, X> implements DataStore<T> {
	protected Set<String> tables;
	protected JdbcTemplate template;
	protected ViennaContext ctx;

	protected AbstractDatabaseDataStore(ViennaContext ctx, JdbcTemplate template) {
		this.ctx = ctx;
		this.template = template;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <E extends T> E load(Class<E> type, Object identifier) {
		StorageMetadata<E> metadata = StorageMetadata.create(ctx, type);
		metadata.identifier = identifier;

		X data = read(metadata);
		return map(data, metadata);
	}

	protected abstract <E extends T> X read(StorageMetadata<E> metadata);

	protected abstract <E extends T> E map(X data, StorageMetadata<E> metadata);

	@Override
	@SuppressWarnings("unchecked")
	public Object save(T obj) {
		StorageMetadata metadata = StorageMetadata.create(ctx, obj);

		Serializer<T> serializer = ctx.getSerializer(metadata.type);
		Map<String, Object> map = serializer.save(obj);
		map.remove(metadata.identifierName);

		if (metadata.identifier == null) {
			metadata.identifier = ctx.getIdentifierGenerator(metadata.type).generate(obj);
			metadata.identifier = insert(metadata);
			ctx.getMetadataProvider((Class<T>) obj.getClass()).setIdentifier(obj, metadata.identifier);
			return metadata.identifier;
		} else {
			update(metadata);
			return metadata.identifier;
		}
	}

	protected abstract Object insert(StorageMetadata<T> metadata);

	protected abstract void update(StorageMetadata<T> metadata);

	protected Set<String> getTables() {
		if (tables == null) {
			try {
				tables = getTablesFromMetadata();
			} catch (MetaDataAccessException e) {
				throw new RuntimeException(e);
			}
		}
		return tables;
	}

	@SuppressWarnings("unchecked")
	protected Set<String> getTablesFromMetadata() throws MetaDataAccessException {
		return (Set<String>) JdbcUtils.extractDatabaseMetaData(template.getDataSource(), new DatabaseMetaDataCallback() {
			@Override
			public Object processMetaData(DatabaseMetaData dbmd) throws SQLException {
				Set<String> names = new HashSet<String>();
				ResultSet rs = dbmd.getTables(null, null, "%", new String[]{"TABLE"});
				while (rs.next()) {
					names.add(rs.getString(3));
				}
				return names;
			}
		});
	}
}
